#include "AbsMachCode.h"

//#define DEBUG
#undef DEBUG

// Startin from register 20, 0-19 were dedicated to event matcher & bitnot
static int registerCount[2] = {20,10};
string newRegister(bool isFloat) {
	char reg[10];
	for(int i=0; i<10; i++)
		reg[i] = '\0';

	if (isFloat)
		sprintf(reg, "F%.3d", registerCount[1]++);
	else 
		sprintf(reg, "R%.3d", registerCount[0]++);

	string s(reg);
	return s;
}

void AbsMachCode::generate (ImmCodes *icode, ostream &os) {
	#ifdef DEBUG
	os << "//[Entering] AbsMachCode::generate()" << endl;
	#endif

	vector<ImmCode*> *ic = icode->get_code(); //*ic is same as ImmCodes::m_codes
	vector<ImmCode*>::iterator it = (*ic).begin();

	/* Produce Machine code for each ImmCode */
	for(; it != (*ic).end(); it++) {
		if (!immToMac(*it, os))
			os << endl;
	}
}

void AbsMachCode::_saveRegisters(FunctionEntry* fe,ostream &os) {
	vector <string> regMap;
	if (fe && fe->symTab()) {
		SymTab::iterator it = fe->symTab()->begin();
		for (;it!=fe->symTab()->end();++it) {
			if ( (*it) && (*it)->kind() == SymTabEntry::VARIABLE_KIND) {
				/* check local variables only (including params) */
				VariableEntry* var = (VariableEntry*)(*it);
				regMap.push_back(var->registerName());
			}
		}
	}
	if (regMap.size()==0) return;
	vector <string>::iterator it = regMap.begin();
	for (;it!=regMap.end();it++) {
		if ((*it)[0]=='R')
			os << "STI ";
		else 
			os << "STF ";
		os << (*it) << SP << endl;
		os << "SUB " << SP << 1 << SP << endl;
	}
}

void AbsMachCode::_restoreRegisters(FunctionEntry* fe,ostream &os) {
	vector <string> regMap;
	if (fe && fe->symTab()) {
		SymTab::iterator it = fe->symTab()->begin();
		for (;it!=fe->symTab()->end();++it) {
			if ( (*it) && (*it)->kind() == SymTabEntry::VARIABLE_KIND) {
				/* check local variables only (including params) */
				VariableEntry* var = (VariableEntry*)(*it);
				regMap.push_back(var->registerName());
			}
		}
	}

	if (regMap.size()==0) return;
	vector <string>::reverse_iterator it = regMap.rbegin();
	for (;it<regMap.rend();++it) {
		os << "ADD " << SP << 1 << SP << endl;
		if ((*it)[0]=='R')
			os << "LDI ";
		else 
			os << "LDF ";
		os << SP << (*it) << endl;
	}
}

inline Type* FTYPE(ExprNode* x) { 
	if (x && x->coercedType()) return (Type*)x->coercedType();
	if (x) return x->type();
	return NULL;}
#define IS_FLT(x)		Type::isFloat((x)->tag())
#define IS_STR(x)		Type::isString((x)->tag())
#define IS_FUNC(x)		((x)->tag() == Type::FUNCTION)

string AbsMachCode::_convertInt2Float(ExprNode* from, ostream &os) {
	os << "MOVIF ";
	os << from->registerName() << " ";
	string newReg = newRegister(true);
	os << newReg << endl;
	return newReg;
}
string AbsMachCode::_convertFloat2Int(ExprNode* from, ostream &os) {
	os << "MOVFI ";
	os << from->registerName() << " ";
	string newReg = newRegister();
	os << newReg << endl;
	return newReg;
}

bool AbsMachCode::immToMac(ImmCode *ic, ostream &os) {
	string regDst;
	string regSrc1;
	string regSrc2;
	static int params = 0;
	static bool lastWasLabel = false;
	
	ImmCode** opnd = (ImmCode**)ic->get_opnd();
	
	switch(ic->get_op()) {
		case EXPR:
		{
			ExprNode** opd = (ExprNode**)ic->get_opnd();
			ExprNode* dst = opd[0];
			ExprNode* src1 = opd[1];
			ExprNode* src2 = opd[2];
			Type* tDst = FTYPE(dst);
			Type* tSrc1 = FTYPE(src1);
			Type* tSrc2 = FTYPE(src2);
			if (dst) regDst = dst->registerName();
			if (src1) regSrc1 = src1->registerName();
			if (src2) regSrc2 = src2->registerName();
			switch(ic->get_subcode()) {
				case OpNode::PLUS:
				case OpNode::MINUS:
				case OpNode::MULT:
				case OpNode::DIV:
				case OpNode::MOD:
				{
					/* convert */
					if (IS_FLT(tDst)) {
						if (!IS_FLT(tSrc1)) 
							regSrc1 = _convertInt2Float(src1, os);
						if (!IS_FLT(tSrc2))
							regSrc2 = _convertInt2Float(src2, os);

						if (ic->get_subcode() == OpNode::PLUS)
							os << "FADD ";
						else if (ic->get_subcode() == OpNode::MINUS)
							os << "FSUB ";
						else if (ic->get_subcode() == OpNode::MULT)
							os << "FMUL ";
						else if (ic->get_subcode() == OpNode::DIV)
							os << "FDIV ";
					} else {
						if (IS_FLT(tSrc1))
							regSrc1 = _convertFloat2Int(src1, os);
						if (IS_FLT(tSrc2))
							regSrc2 = _convertFloat2Int(src2, os);

						if (ic->get_subcode() == OpNode::PLUS)
							os << "ADD ";
						else if (ic->get_subcode() == OpNode::MINUS)
							os << "SUB ";
						else if (ic->get_subcode() == OpNode::MULT)
							os << "MUL ";
						else if (ic->get_subcode() == OpNode::DIV)
							os << "DIV ";
						else if (ic->get_subcode() == OpNode::MOD)
							os << "MOD ";
					}
					os << regSrc1 << " " << regSrc2 << " ";
					os << regDst;
				}
				lastWasLabel = false;
				break;

				/* Never used--- Already filtered at Intermediate code phase
				case OpNode::EQ:
				case OpNode::NE:
				case OpNode::GT:
				case OpNode::LT:
				case OpNode::GE:
				case OpNode::LE:
				case OpNode::AND:
				case OpNode::OR:*/
				case OpNode::BITAND:
				case OpNode::BITOR:
				case OpNode::BITXOR:
					if (IS_FLT(tSrc1)) {
						os << "MOVFI " << regSrc1 << " ";
						regSrc1 = newRegister();
						os << regSrc1;
					}
					if (IS_FLT(tSrc2)) {
						os << "MOVFI " << regSrc2 << " ";
						regSrc2 = newRegister();
						os << regSrc2;
					}
					if (ic->get_subcode() == OpNode::BITAND) os << "AND ";
					if (ic->get_subcode() == OpNode::BITOR) os << "OR ";
					if (ic->get_subcode() == OpNode::BITXOR) os << "XOR ";
					
					os << regSrc1 << " " << regSrc2 << " ";
					if (IS_FLT(tDst)) {
						string newReg = newRegister();
						os << newReg;
						os << "MOVIF " << newReg << " " << regDst;
					} else {
						os << regDst;
					}
					lastWasLabel = false;
					break;
				case OpNode::SHL:
				case OpNode::SHR:
					/* save counter */
					{
						os << "MOVI " << regSrc2 << " " << SL << endl;
						ImmCode* newLabel = ImmCodes::newLabel();
						ImmCode* newLabelOut = ImmCodes::newLabel();
						// save init val to dst 
						os << "MOVI " << regSrc1 << " " << regDst << endl;
						os << newLabel->labelName() << ": ";
						os << "JMPC GE " << 0 << SL << newLabelOut->labelName() << endl;
						if (ic->get_subcode() == OpNode::SHL) 
							os << "MUL ";
						else os << "DIV ";
						os << regDst << " " << 2 << " ";
						os << regDst << endl;
						os << "SUB " << SL << 1 << SL << endl;
						os << "JMP " << newLabel->labelName() << endl;
						os << newLabelOut->labelName() << ": ";
						lastWasLabel = true;
					}
					break;
				/*case OpNode::UMINUS:  // filtered in 3 address code phase */
				case OpNode::NOT:
					{
						ImmCode* newTrue = ImmCodes::newLabel();
						ImmCode* newFalse = ImmCodes::newLabel();
						os << "JMPC NE " << regSrc1 << " " << 0 << " ";
						os << newFalse->labelName() << endl;
						os << "MOVI 1 " << regDst << endl;
						os << "JMP " << newTrue->labelName() << endl;
						os << newFalse->labelName() << ": ";
						os << "MOVI 0 " << regDst << endl;
						os << newTrue->labelName() << ": ";
						lastWasLabel = true;
						break;
					}
				case OpNode::BITNOT:
					{
						ImmCode* l1 = ImmCodes::newLabel();
						ImmCode* l2 = ImmCodes::newLabel();
						ImmCode* l3 = ImmCodes::newLabel();
						ImmCode* l4 = ImmCodes::newLabel();
						ImmCode* l5 = ImmCodes::newLabel();
						ImmCode* l6 = ImmCodes::newLabel();
						ImmCode* l7 = ImmCodes::newLabel();

						/* bitnot function developed in E--, and then 
							converted to final code. The final code 
							used here to support bitnot!!! */
/*
MOVI 0 GR0 
MOVI 0 GR1
L1: JMPC UGE GR0 32 L2
MOVI GR0 SL
MOVI <src> GR2 
L5: JMPC GE 0 SL  L4
DIV GR2 2 GR2
SUB  SL 1 SL
JMP L5
L4: AND GR2 1 GR3
JMPC NE GR3 0 L3
MOVI GR0  SL
MOVI 1 GR4 
L6: JMPC GE 0 SL L7
MUL GR4 2 GR4
SUB  SL 1  SL
JMP L6
L7: ADD GR1 GR4 GR6
MOVI GR6 GR1 
L3: ADD GR0 1 GR5
MOVI GR5 GR0
JMP L1
L2:*/

						os << "MOVI 0 " << GR0 << endl;
						os << "MOVI 0" << GR1 << endl;
						os << l1->labelName() << ": " << "JMPC UGE" << GR0<<" 32 " << l2->labelName() << endl;
						os << "MOVI " << GR0 <<  SL << endl;
						os << "MOVI " << regSrc1 << GR2 << endl;
						os << l5->labelName() << ": " << "JMPC GE 0 " << SL << l4->labelName() << endl;
						os << "DIV " << GR2 << 2 << GR2 << endl;
						os << "SUB  " << SL <<  1 <<  SL << endl;
						os << "JMP " << l5->labelName() << endl;
						os << l4->labelName() << ": " << "AND " << GR2 << 1 << GR3 << endl;
						os << "JMPC NE " << GR3 << 0 << l3->labelName() << endl;
						os << "MOVI " << GR0 << SL << endl;
						os << "MOVI 1 " << GR4 << endl;
						os << l6->labelName() << ": JMPC GE 0 " << SL <<  l7->labelName() << endl;
						os << "MUL " << GR4 << 2 << GR4 << endl;
						os << "SUB " << SL << 1 << SL << endl;
						os << "JMP " << l6->labelName() << endl;
						os << l7->labelName() << ": " << "ADD " << GR1 << GR4 << GR6 << endl;
						os << "MOVI " << GR6 << GR1 << endl; 
						os << l3->labelName() << ": " << "ADD " << GR0 << 1 << GR5 << endl;
						os << "MOVI " << GR5 << GR0 << endl;
						os << "JMP " << l1->labelName() << endl;
						os << l2->labelName() << ": ";
						os << "MOVI " << GR1 << regDst;
					}
					lastWasLabel = false;
					break;
				case OpNode::ASSIGN:
					if (IS_FLT(tDst)) {
						// Float -> Float
						if (IS_FLT(tSrc1)) os << "MOVF ";
						// Int -> Float
						else os << "MOVIF ";
					} else if (IS_STR(tDst)) {
						os << "MOVS ";
					} else {
						// Float -> Int 
						if (IS_FLT(tSrc1)) os << "MOVFI ";
						// Int -> Int
						else os << "MOVI ";
					}
					os << regSrc1 << " " << regDst;
					lastWasLabel = false;
					break;
				case OpNode::PRINT:
					{
						ExprNode** opd = (ExprNode**)ic->get_opnd();
						ExprNode* src1 = opd[1];
						Type* tSrc1 = FTYPE(src1);
						if (IS_STR(tSrc1))
							os << "PRTS " << src1->registerName();
						else if (IS_FLT(tSrc1))
							os << "PRTF " << src1->registerName();
						else
							os << "PRTI " << src1->registerName();
					}
					lastWasLabel = false;
					break;
			};
		}
		break;
		case IF:
			{
				ImmCode *label = (ImmCode*)opnd[0];
				ExprNode** opd = (ExprNode**)ic->get_opnd();
				Type* tSrc1 = FTYPE(opd[1]);
				if (IS_FLT(tSrc1)) 
					os << "JMPC FNE " << opd[1]->registerName() << " 0.0 ";
				else
					os << "JMPC NE " << opd[1]->registerName() << " 0 ";
				os << label->labelName();
			}
			lastWasLabel = false;
			break;
		case IF_REL:
			// 1-e1.addr , 2-e2.addr 0-goto
			{
				ImmCode *label = (ImmCode*)opnd[0];
				ExprNode** opd = (ExprNode**)ic->get_opnd();
				ExprNode* src1 = opd[1];
				ExprNode* src2 = opd[2];
				Type* tSrc1 = FTYPE(src1);
				Type* tSrc2 = FTYPE(src2);
				if (src1) regSrc1 = src1->registerName();
				if (src2) regSrc2 = src2->registerName();
				
				bool flt = IS_FLT(tSrc1) || IS_FLT(tSrc2);
				if (flt) {
					if (!IS_FLT(tSrc1)) regSrc1 = _convertInt2Float(src1, os);
					if (!IS_FLT(tSrc2)) regSrc2 = _convertInt2Float(src2, os);
					os << "JMPC ";
					os << "F";	// start with F**
				} else
					os << "JMPC ";
				bool unsn = (Type::isUnsigned(tSrc1->tag()) &&
						Type::isUnsigned(tSrc2->tag()));
				switch(ic->get_subcode()) {
					case OpNode::EQ:
					case OpNode::NE:
						os << OpNode::OpCodeStr(ic->get_subcode()) << " ";
					break;
					case OpNode::GT:
					case OpNode::GE:
					case OpNode::LT:
					case OpNode::LE:
						if (unsn && !flt) os << "U";
						if (ic->get_subcode()==OpNode::GT || 
							ic->get_subcode()==OpNode::LT) 
							os << OpNode::OpCodeStr(OpNode::GT) << " ";
						else 
							os << OpNode::OpCodeStr(OpNode::GE) << " ";
						/* LE,LE are not supported directly */
						/* swap operand */
						if (ic->get_subcode()==OpNode::LT ||
							ic->get_subcode()==OpNode::LE) {
							string regTmp = regSrc1;
							regSrc1 = regSrc2;
							regSrc2 = regTmp;
						}
					break;
				}
				os << regSrc1 << " " << regSrc2 << " ";
				os << label->labelName();
			}
			lastWasLabel = false;
			break;
		case GOTO:
			{
				ImmCode* label = (ImmCode*)opnd[0];
				os << "JMP " << label->labelName();
			}
			lastWasLabel = false;
			break;
		case LABEL:
			{
				unsigned int id = (long)opnd[0];
				string *name = (string*)opnd[1];

				if (lastWasLabel) {
					os << "ADD " << SP << 0 << SP << endl;
				}

				if (name && name->length())
					os << *name << ": ";
				else
					os << "L" << id << ": ";
				lastWasLabel = true;
			}
			break;
		case PARAM:
			{
				ExprNode** opd = (ExprNode**)ic->get_opnd();
				ExprNode* param = opd[0];
				InvocationNode* iv = (InvocationNode*)opnd[1];
				FunctionEntry *fe = (FunctionEntry*)iv->symTabEntry();
				if (params == 0) {
					/* save registers */
					_saveRegisters(fe, os);os << endl;
				}
				string regName;
				if (param->exprNodeType()==ExprNode::VALUE_NODE) {
					if (IS_FLT(FTYPE(param))) {
						regName = GF0;
						os << "MOVF " << param->registerName() << regName << endl;
					} else {
						regName = GR0;
						os << "MOVI " << param->registerName() << regName << endl;
					}
				} else {
					regName = param->registerName();
				}

				if (IS_FLT(FTYPE(param)))
					os << "STF " << regName << SP << endl;
				else
					os << "STI " << regName << SP << endl;
				os << "SUB " << SP << 1 << SP;
				params++;
			}
			lastWasLabel = false;
			break;
		case INLINE:
			{
				ExprNode** opd = (ExprNode**)ic->get_opnd();
				ExprNode* dst = opd[1];
				ExprNode* src1 = opd[2];
				Type* tDst = FTYPE(dst);
				Type* tSrc1 = FTYPE(src1);
				if (dst) regDst = dst->registerName();
				if (src1) regSrc1 = src1->registerName();
				if (IS_FLT(tDst)) {
					// Float -> Float
					if (IS_FLT(tSrc1)) os << "MOVF ";
					// Int -> Float
					else os << "MOVIF ";
				} else if (IS_STR(tDst)) {
					os << "MOVS ";
				} else {
					// Float -> Int 
					if (IS_FLT(tSrc1)) os << "MOVFI ";
					// Int -> Int
					else os << "MOVI ";
				}
				os << regSrc1 << " " << regDst;
			}
			lastWasLabel = false;
			break;
		case CALL:
			{
				InvocationNode* iv = (InvocationNode*)opnd[0];
				FunctionEntry *fe = (FunctionEntry*)iv->symTabEntry();
				ExprNode** opd = (ExprNode**)ic->get_opnd();
				ExprNode* retVal = opd[1];
				/* allocate new label */
				ImmCode* newLabel = ImmCodes::newLabel();
				if (params == 0) {
					/* save registers */
					_saveRegisters(fe, os);
				}
				/* push space for return value */
				os << "SUB " << SP << 1 << SP << "// space for return value" << endl;
				/* push return address */
				os << "MOVL "<< newLabel->labelName() << LR << endl;
				os << "STI " << LR << SP << endl;
				os << "SUB " << SP << 1 << SP << endl;
				os << "JMP " << fe->name() << endl;
				os << newLabel->labelName() << ": ";
				/* retrieve return value */
				os << "ADD " << SP << 3 << SP;
				Type* tDst = FTYPE(iv->addr());
				if (tDst) {
					os << "// copy return value" << endl;
					if (IS_FLT(tDst))
						os << "LDF ";
					else
						os << "LDI ";
					os << SP << iv->addr()->registerName() << endl;
				} else os << endl;
				/* pop params & more */
				os << "ADD " << SP << params << SP << endl;
				/* restore registers */
				_restoreRegisters(fe, os);
				params=0;
			}
			lastWasLabel = false;
			break;
		case ENTER:
			{
				FunctionEntry* fe = (FunctionEntry*)opnd[0];
				/* save bp */
				os << "STI " << BP << SP << endl;
				os << "SUB " << SP << 1 << SP << endl;
				/* set new bp */
				os << "MOVI " << SP << BP << endl;
				/* backup registers (used in this function*/

				/* init parameter */
				int paramIndex=0;
				if (fe->symTab()) {
					SymTab::iterator it = fe->symTab()->begin();
					for (;it!=fe->symTab()->end();++it) {
						if ( (*it) && (*it)->kind() == SymTabEntry::VARIABLE_KIND) {
							VariableEntry* var = (VariableEntry*)(*it);
							if (var->varKind() == VariableEntry::PARAM_VAR) {
								os << "// init param" << paramIndex << endl;
								if (paramIndex==0)
									os << "ADD " << BP << 4 << BP << endl;
								else
									os << "ADD " << BP << 1 << BP << endl;
								paramIndex++;
								if (Type::isFloat(var->type()->tag())) 
									os << "LDF ";
								else os << "LDI ";
								os << BP << var->registerName();
							}
						}
					}
				}
				// restore bp
				if (paramIndex) {
					os << "// restore bp" << endl;
					os << "SUB " << BP << 3 + paramIndex << BP << endl;
				}
			}
			lastWasLabel = false;
			break;
		case LEAVE:
			{
				FunctionEntry* fe = (FunctionEntry*)opnd[1];
				ExprNode** opd = (ExprNode**)ic->get_opnd();
				ExprNode* ret = opd[0];
				/* set return value */
				os << "ADD " << BP << 3 << BP;
				os << "// set return value" << endl;
				if (ret) {
					Type* tRet = FTYPE(ret);
					if (IS_FLT(tRet))
						os << "STF ";
					else os << "STI ";
					os << ret->registerName() << BP << endl;
				}
				os << "SUB " << BP << 1 << BP << endl;
				/* get return address */
				os << "// get return address" << endl;
				os << "LDI " << BP << LR << endl;
				/* restore bp */
				os << "// restore BP to caller's BP" << endl;
				os << "SUB " << BP << 1 << BP << endl;
				os << "LDI " << BP << BP << endl;
				/* jump to caller */
				os << "JMPI " << LR;
			}
			lastWasLabel = false;
			break;
		default:
			break;
	};
	return lastWasLabel;
}
